package com.type.TypingDetector;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.List;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.pm.ActivityInfo;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.os.SystemClock;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.ToggleButton;

public class TypingDetector extends Activity {
	private SensorManager sm;
    private List<Sensor> sensors;
    private Sensor accel; //Primary Accelerometer
    boolean accelerometerAvailable = false; //If an accelerometer is available or not
    TextView predictText; //TextView for displaying result
    ToggleButton enabled, streaming; //Whether prediction is enabled or not
    EditText testText;
    private boolean active=false, first=true; //
    private int counter; //current counter of the dataArr
    private double[][] data;
    private double oldVal;
    private double[] differences;
    private final int DATA_SIZE=120; //size of data array
    private final int DATA_NUMBERS=3;
    private final int WATCH_SIZE=40; //Number of samples which need to pass before we make prediction
    
    private String predicted=""; //result string
    private int watchCounter; //Used if a change in activity is suspected
    private boolean currentlyWatching=false; //
    private InfoDatObject[] staticVals;
    FourierTransform ft;
    private int startCounter;
    private final int SMOOTH_ITERATIONS=1;
    private boolean restart=false;
    private final String PATH_TO_FILE="data/data/com.type.TypingDetector";
    private final String FOLDER_NAME="SavedFiles";
    private final String CLASSES="Right, Left, Top, Bottom";
    private String flenm;
    private final int START_OFFSET=5;
    
    //Database Stuff
    protected final String DATABASE_TABLE="AccelValues4";
    protected final String FN_TABLE="Filenames4";
    protected ArrayList<String> colNamesT= new ArrayList<String>(Arrays.asList("Title", "XDom", "XAvg", "XMax", "XMin", "YDom", "YAvg", "YMax", "YMin", "ZDom", "ZAvg", "ZMax", "ZMin", "Filename"));
    protected ArrayList<String> colOptsT= new ArrayList<String>(Arrays.asList("text","real","real","real","real","real","real","real","real","real","real","real","real", "text"));
    protected ArrayList<String> colNames= new ArrayList<String>(Arrays.asList("Title", "XDom", "XAvg", "XMax", "XMin", "YDom", "YAvg", "YMax", "YMin", "ZDom", "ZAvg", "ZMax", "ZMin"));
    protected ArrayList<String> colOpts= new ArrayList<String>(Arrays.asList("text","real","real","real","real","real","real","real","real","real","real","real","real"));
    protected ArrayList<String> fntbColNames = new ArrayList<String>(Arrays.asList("Type","Filename"));
    protected ArrayList<String> fntbColOpts = new ArrayList<String>(Arrays.asList("text","text"));
    private DatabaseQuery query, fnQuery;
    private final String dbRowIDName="_id";
    
    //Classification Constants
    private final int NEAREST_NEIGHBOR_ALL=0;
    private final int NEAREST_NEIGHBOR_NOMM=1;
    private int CURRENT_CHOICE=NEAREST_NEIGHBOR_ALL;
    
    //TCP-IP Socket Communication
    private boolean mSocketAvl=false;
    private Socket socket; //Socket used to communicate with server
    private String serverPacket;
    private final String mIPAddr="128.2.142.39";
    private final int portNo=6002;
    private BufferedReader in;
    private PrintWriter out;
    private final String TRANSMIT_OVER="Done";
    private final String DATABASE_DATA_FN="DBData";
    
    public void onCreate(Bundle savedInstanceState) {
    	super.onCreate(savedInstanceState);
    	setContentView(R.layout.type_prediction);
    	setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
    	
    	AlertDialog.Builder builder = new AlertDialog.Builder(this);
        final AlertDialog settings;
        
        builder.setCancelable(false);
        LayoutInflater inflater = (LayoutInflater) this.getSystemService(LAYOUT_INFLATER_SERVICE);
        View layout = inflater.inflate(R.layout.stream_dialog,
                null);
        
        final EditText et=(EditText) layout.findViewById(R.id.editTextFN);
        builder.setView(layout);
        builder.setCancelable(false);
        builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
			
			@Override
			public void onClick(DialogInterface dialog, int which) {
				// TODO Auto-generated method stub
				if (!et.getText().toString().equals(""))
					flenm=""+et.getText();
				else {
					String tmp;
					java.sql.Timestamp ct=new java.sql.Timestamp(Calendar.getInstance().getTime().getTime());
	            	tmp=ct.toString();
	            	tmp=tmp.substring(5,10)+"-"+tmp.substring(11,19);
					flenm=tmp;
				}
				activateStream();
				dialog.dismiss();
			}
		});
        
        settings=builder.create();
        
    	predictText=(TextView) findViewById(R.id.predictText);
    	enabled=(ToggleButton) findViewById(R.id.togglebutton2);
    	streaming=(ToggleButton) findViewById(R.id.tbstream);
    	testText=(EditText) findViewById(R.id.testText);
    	counter=0;
    	data=new double[DATA_SIZE][DATA_NUMBERS];
    	oldVal=0.0;
    	differences=new double[DATA_SIZE];
    	
    	
    	streaming.setOnClickListener(new OnClickListener() {

			@Override
			public void onClick(View v) {
				// TODO Auto-generated method stub
				if(((ToggleButton)v).isChecked()) {
			    	 settings.show();
				}
				else {
					if (mSocketAvl) {
						try {
							out.println(TRANSMIT_OVER);
							socket.close();
							in.close();
						} catch (IOException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
							Toast.makeText(TypingDetector.this, "Connection Termination Failure", Toast.LENGTH_SHORT);
						}
						Toast.makeText(TypingDetector.this, "Connection Termination Successful", Toast.LENGTH_SHORT);
						out.close();
						mSocketAvl=false;
					}
				}
			}
    	});
    	
    	if (query==null) query=new DatabaseQuery(this, colNamesT, colOptsT, DATABASE_TABLE);
    	if (fnQuery==null) fnQuery=new DatabaseQuery(this, fntbColNames, fntbColOpts, FN_TABLE);
    	ArrayList<String> dbVals=query.getDataNS(colNames.toArray(new String[colNames.size()]), null, null, null, null);
    	staticVals=new InfoDatObject[dbVals.size()/colNames.size()];
    	
    	int ctr=0;
    	for (int i=0; i<dbVals.size(); i+=colNames.size()) {
			  //Comparison c = new Comparison(dbVals.get(i), Double.valueOf(dbVals.get(i+1)), Double.valueOf(dbVals.get(i+2)), Double.valueOf(dbVals.get(i+3)), Double.valueOf(dbVals.get(i+4)));
			  //staticVals[i/5]=c;
    		String nam=dbVals.get(i);
    		double[] frqs=new double[] {Double.valueOf(dbVals.get(i+1)), Double.valueOf(dbVals.get(i+5)), Double.valueOf(dbVals.get(i+9))};
    		double[] avs=new double[] {Double.valueOf(dbVals.get(i+2)), Double.valueOf(dbVals.get(i+6)), Double.valueOf(dbVals.get(i+10))};
    		double[] mxs=new double[] {Double.valueOf(dbVals.get(i+3)), Double.valueOf(dbVals.get(i+7)), Double.valueOf(dbVals.get(i+11))};
    		double[] mns=new double[] {Double.valueOf(dbVals.get(i+4)), Double.valueOf(dbVals.get(i+8)), Double.valueOf(dbVals.get(i+12))};
    		staticVals[ctr]=new InfoDatObject(nam, frqs, avs, mxs, mns);
    		ctr++;
		}
    	
    	enabled.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
				// TODO Auto-generated method stub
				active=enabled.isChecked();
				if (!active) {
					restart=true;
				}
				testText.clearComposingText();
				testText.setEnabled(active);
			}
        });
    	
    	accelerometerAvailable = instantiateSensors();
    	
    	if (accelerometerAvailable)
        {
        	sm.registerListener(mySEL, accel, SensorManager.SENSOR_DELAY_FASTEST);
        }
    }
    
    
    private boolean instantiateSensors() {
		// TODO Auto-generated method stub
		sm = (SensorManager)getSystemService(Context.SENSOR_SERVICE);
        sensors = sm.getSensorList(Sensor.TYPE_ACCELEROMETER);
        
        if (sensors.size() > 0) {
        	accel = sensors.get(0);
        	return true;}
        return false;
    }
    
    private final SensorEventListener mySEL = new SensorEventListener() {

		@Override
		public void onAccuracyChanged(Sensor sensor, int accuracy) {
			// TODO Auto-generated method stub
			
			
		}

		@Override
		public void onSensorChanged(SensorEvent event) {
			// TODO Auto-generated method stub
			float[] vals=event.values;
			for (int i=0; i<vals.length; i++) {
				data[counter][i]=(double)vals[i];
			}
			
			double mag=Math.sqrt(Math.pow(data[counter][0], 2)+Math.pow(data[counter][1], 2)+Math.pow(data[counter][2], 2));
			differences[counter]=mag-oldVal;
			oldVal=mag;
			
			//predictText.setText(""+counter);
			if (active || restart) {
				if (!currentlyWatching && (Math.abs(differences[40])>0.0)) {
					if (spike()) {
						int diff=counter-START_OFFSET;
						if (diff<0) {startCounter=diff+DATA_SIZE;}
						else {startCounter=diff;}
						watchCounter=(startCounter+WATCH_SIZE)%(DATA_SIZE);
						currentlyWatching=true;
						restart=false;
					}
				}
				else {
					if (counter==watchCounter) {
						double[][] temp;
						int diff = watchCounter-startCounter;
						if (diff>0) {temp=new double[diff][DATA_NUMBERS];} //if normal
						else {temp=new double[diff+DATA_SIZE][DATA_NUMBERS];} //if wrap around
						for (int j=0; j<temp.length; j++) {
							for (int k=0; k<DATA_NUMBERS; k++)
								temp[j][k]=data[(startCounter+j)%DATA_SIZE][k];
						}
						InfoDatObject id=makePrediction(temp);
						//predicted += ", " + id.getName();
						//predictText.setText(predicted);
						if (mSocketAvl) {
							out.println(writeServerPacket(id));
							String readFromServer;
							try {
								readFromServer = in.readLine();
								if (readFromServer!=null && !readFromServer.equals("Idle")) {
									if (predicted.equals(""))
										predicted+=readFromServer;
									else
										predicted+= ", " + readFromServer;
								}
								
							} catch (IOException e) {
								// TODO Auto-generated catch block
								e.printStackTrace();
								predicted+= ", Fail";
							}
							predictText.setText(predicted);
							
						}
						currentlyWatching=false;
						watchCounter=-1;
					}
				}
			}
			
			counter++;
			counter%=DATA_SIZE;
		}
    	
    };
    
    /** This method parses files to initialize the staticVals array with the data accrued from LearningMode.
	 * 
	 * @param strm The Input Stream of the files to be read
	 * @param index The PREDICTION_TYPE index indicating the type of data the current Stream is referring too
	 * @return A comparison object with the average and fundamental frequency
	 * @throws FileNotFoundException
	 */
	private InfoDatObject makePrediction(double[][] temp) {
		/*Scanner scan=new Scanner(strm);
		ArrayList<Double> data=new ArrayList<Double>(); //don't know size, so ArrayList
		scan.useDelimiter(",");
		scan.nextLine(); //throw out first line
		while (scan.hasNextLine()) { //must be formatted as xval, yval, zval
			String temp=scan.nextLine();
			if (temp!="") {
				double storVal = Double.valueOf(temp);
				data.add(storVal);
			}
		}*/
		
		double[] datX=temp[0];
		double[] datY=temp[1];
		double[] datZ=temp[2];
		
		
		for (int j=0; j<SMOOTH_ITERATIONS; j++) {
			datX=AcceleromterUpdated.smoothValues(datX);
			datY=AcceleromterUpdated.smoothValues(datY);
			datZ=AcceleromterUpdated.smoothValues(datZ);
		}
		
		double[] datRX = new double[FourierTransform.nextPowerOfTwo(datX.length)];
		double[] datIX = new double[datRX.length];
		double[] datRY = new double[FourierTransform.nextPowerOfTwo(datY.length)];
		double[] datIY = new double[datRY.length];
		double[] datRZ = new double[FourierTransform.nextPowerOfTwo(datZ.length)];
		double[] datIZ = new double[datRZ.length];
		
		double datMaxX=0,datMaxY=0,datMaxZ=0;
		double datMinX=Double.MAX_VALUE, datMinY=Double.MAX_VALUE, datMinZ=Double.MAX_VALUE;
		double cTotX=0.0, cTotY=0.0, cTotZ=0.0;
		
		for (int j=0; j<datRX.length; j++) {
		 	if (j<datX.length) {
		 		if (datX[j]>datMaxX) {
		 			datMaxX=datX[j];
		 		}
		 		if (datX[j]<datMinX) {
		 			datMinX=datX[j];
		 		}
		 		if (datY[j]>datMaxY) {
		 			datMaxY=datY[j];
		 		}
		 		if (datY[j]<datMinY) {
		 			datMinY=datY[j];
		 		}
		 		if (datZ[j]>datMaxZ) {
		 			datMaxZ=datZ[j];
		 		}
		 		if (datZ[j]<datMinZ) {
		 			datMinZ=datZ[j];
		 		}
		 		datRX[j] = datX[j]; datRY[j] = datY[j]; datRZ[j] = datZ[j];
				cTotX+=datRX[j]; cTotY+=datRY[j]; cTotZ+=datRZ[j];
			}
			else { //pad with 0s
				datRX[j]=0.0; datRY[j]=0.0; datRZ[j]=0.0;
			}
			datIX[j]=0.0; datIY[j]=0.0; datIZ[j]=0.0;
		}
		
		double avgX = cTotX/(datX.length), avgY=cTotY/(datY.length), avgZ=cTotZ/(datZ.length);
		FastFourierTransform.fastFT(datRX, datIX, true);
		FastFourierTransform.fastFT(datRY, datIY, true);
		FastFourierTransform.fastFT(datRZ, datIZ, true);
		double fFreqX = findAvgFundamentalFreq(datRX, datIX);
		double fFreqY = findAvgFundamentalFreq(datRY, datIY);
		double fFreqZ = findAvgFundamentalFreq(datRZ, datIZ);
		//scan.close();
		InfoDatObject ido=new InfoDatObject("", new double[] {fFreqX, fFreqY, fFreqZ}, new double[] {avgX, avgY, avgZ}, new double[] {datMaxX, datMaxY, datMaxZ}, new double[] {datMinX, datMinY, datMinZ});
		if (mSocketAvl) {
			/*serverPacket=writeServerPacket(ido);
			 *out.print(serverPacket);
			 */
		}
		return findClosestMatch(ido,staticVals, CURRENT_CHOICE);
		//return "";
	}
	
	private String writeServerPacket(InfoDatObject ido) {
		String retVal="";
		String dlm=",";
		String titleReplacement=" ";
		double[] fFreqs=ido.getfFreqs();
		double[] avs=ido.getAvgs();
		double[] mxs=ido.getMaxes();
		double[] mns=ido.getMins();
		
		retVal+= fFreqs[0] + dlm + fFreqs[1] + dlm + fFreqs[2] +
			dlm + avs[0] + dlm + avs[1] + dlm + avs[2] + dlm + mxs[0] + dlm +
			mxs[1] + dlm + mxs[2] + dlm + mns[0] + dlm + mns[1] + dlm + mns[2] + dlm + "?";
		
		return retVal;
	}
	
	private InfoDatObject findClosestMatch(InfoDatObject ido, InfoDatObject[] vals, int predAlgorithm) {
		String retVal = null;
		double bestMatch=Double.MAX_VALUE;
		switch (predAlgorithm) {
			case NEAREST_NEIGHBOR_ALL:
				for (int i=0; i<vals.length; i++) {
					double currMatch=Math.sqrt(squaredVectorDifferences(ido.getfFreqs(),vals[i].getfFreqs()) +
							squaredVectorDifferences(ido.getAvgs(),vals[i].getAvgs()) + 
							squaredVectorDifferences(ido.getMaxes(),vals[i].getMaxes()) +
							squaredVectorDifferences(ido.getMins(),vals[i].getMins())); //pythagorean distance
					if (currMatch < bestMatch) {
						bestMatch=currMatch;
						retVal=vals[i].getName();
					}
				}
				ido.setName(retVal);
				return ido;
			case NEAREST_NEIGHBOR_NOMM:
				for (int i=0; i<vals.length; i++) {
					double currMatch=Math.sqrt(squaredVectorDifferences(ido.getfFreqs(),vals[i].getfFreqs()) +
							squaredVectorDifferences(ido.getAvgs(),vals[i].getAvgs())); //pythagorean distance
					if (currMatch < bestMatch) {
						bestMatch=currMatch;
						retVal=vals[i].getName();
					}
				}
				ido.setName(retVal);
				return ido;
			default:
				return ido;
		}
	}
	
	private double squaredVectorDifferences(double[] vec1, double[] vec2) {
		double squaredDiffs=0.0;
		for (int i=0; i<vec1.length; i++) {
			squaredDiffs+=Math.pow(vec1[i]-vec2[i],2);
		}
		return squaredDiffs;
	}
	/** Finds the fundamental frequency of the Fourier Transformed data passed in.
	 * 
	 * @param real The real part of the data
	 * @param imag The imaginary part of the data
	 * @return the fundamental frequency of the data
	 */
	public double findFundamentalFreq(double[] real, double[] imag) {
		double currMax = 0;
		double retVal=-1;
		for (int i=0; i <real.length; i++) {
			double mag = Math.sqrt(Math.pow(real[i],2)+Math.pow(imag[i],2));
			if (mag > currMax) retVal=i;
		}
		return retVal;
	}
	
	public double findAvgFundamentalFreq(double[] real, double[] imag) {
		double retVal=-1;
		double[] maxes=new double[3];
		double minMax=0;
		int maxCounter=0;
		
		for (int i=0; i <real.length; i++) {
			double mag = Math.sqrt(Math.pow(real[i],2)+Math.pow(imag[i],2));
			if (mag > minMax) {
				if (maxCounter<2) {
					maxes[maxCounter] = mag;
					maxCounter++;
				}
				else {
					//find lowest of the current maxes
					double min=Double.MAX_VALUE;
					int indexOfLowest=0;
					for (int j=0; j<maxes.length;j++) {
						if (maxes[j]<min) {min=maxes[j];}
						indexOfLowest=j;
					}
					maxes[indexOfLowest]=mag;
					minMax=mag;
				}
			}		
		}
		return (double)((maxes[0]+maxes[1]+maxes[2])/3);
	}
	
	private boolean activateStream() {
		try {
			if (!mSocketAvl) {
				socket=new Socket(mIPAddr, portNo);
				in=new BufferedReader(new InputStreamReader(socket.getInputStream()));
				out=new PrintWriter(socket.getOutputStream(), true);
				mSocketAvl=true;
			}
    	 }
    	 catch (IOException e) {
    	 	mSocketAvl=false;
    	 	Toast.makeText(TypingDetector.this, "Connection Failed", Toast.LENGTH_SHORT);
    	 	testText.setText("Connection Failed");
    	 }
    	 if (mSocketAvl) {
    		 out.println(flenm);
    		 Toast.makeText(TypingDetector.this, "Connection Successful", Toast.LENGTH_SHORT);
    		 testText.setText("Connection Successful");
    		 ArrayList<String> uniqueRNs=fnQuery.getDataNS(new String[] {"Type"}, null, null, null, null);
	    		ArrayList<String> dummy=new ArrayList<String>();
	    		String t="";
	    		for (int i=0; i<uniqueRNs.size();i++) {
	    			if (!dummy.contains(uniqueRNs.get(i))) {
	    				t+=uniqueRNs.get(i)+",";
	    				dummy.add(uniqueRNs.get(i));
	    			}
	    		}
	    		t=t.substring(0, t.length()-1);
	    		out.println(CLASSES);
	    		out.println("U"); //unlabeled dataset
    	 }
    	 return mSocketAvl;
	}
	
	/**
	 * Calculates if there was a spike in the readings or not
	 * @return true if a spike occured, false otherwise
	 */
	public boolean spike() {
		double total=0.0;
		int count=1;
		
		for (int i=0; i<differences.length;i++) {
			if (differences[i]==0.0) break;
			total+=differences[i];
			count++;
		}
		
		double avg=total/count;
		double std=0.0;
		for (int i=0; i<differences.length; i++) {
			std+=Math.pow((differences[i]-avg),2);
		}
		
		std/=count;
		std=Math.sqrt(std);
		
		if (differences[counter]>=avg+std)
			return true;
		return false;
	}
	
    private class InfoDatObject {
    	private String name;
		private double[] fFreqs;
		private double[] avgs;
		private double[] maxes;
		private double[] mins;
		
		public InfoDatObject(String nam, double[] freqs, double[] avs, double[] mxs, double[] mns) {
			setName(nam);
			setfFreqs(freqs);
			setAvgs(avs);
			setMaxes(mxs);
			setMins(mns);
		}
		
		public void setfFreqs(double[] fFreqs) {
			this.fFreqs = fFreqs;
		}
		public double[] getfFreqs() {
			return fFreqs;
		}
		public void setAvgs(double[] avgs) {
			this.avgs = avgs;
		}
		public double[] getAvgs() {
			return avgs;
		}
		public void setMaxes(double[] maxes) {
			this.maxes = maxes;
		}
		public double[] getMaxes() {
			return maxes;
		}
		public void setMins(double[] mins) {
			this.mins = mins;
		}
		public double[] getMins() {
			return mins;
		}

		public void setName(String name) {
			this.name = name;
		}

		public String getName() {
			return name;
		}
	}
}
